using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SkinningInformation
{
	public class AnimationPlayer
	{
		AnimationClip currentClip;
		TimeSpan currentTime;
		int currentKeyframe;
		Matrix[] boneTransforms;
		Matrix[] worldTransforms;
		Matrix[] skinTransforms;
		SkinningData skinningData;
		public AnimationPlayer(SkinningData skinningData)
		{
			if (skinningData == null)
				throw new ArgumentNullException("skinningData");
			this.skinningData = skinningData;
			this.boneTransforms = new Matrix[skinningData.BindPose.Count];
			this.worldTransforms = new Matrix[skinningData.BindPose.Count];
			this.skinTransforms = new Matrix[skinningData.BindPose.Count];
		}

		public Dictionary<string, AnimationClip> AnimationClips
		{
			get
			{
				return this.skinningData.AnimationClips;
			}
		}

		public void StartClip(AnimationClip clip)
		{
			if (clip == null)
				throw new ArgumentNullException("clip");
			currentClip = clip;
			currentTime = TimeSpan.Zero;
			currentKeyframe = 0;
			// Initialize bone transforms to the bind pose.
			skinningData.BindPose.CopyTo(boneTransforms, 0);
		}
		public void Update(TimeSpan time, bool relativeToCurrentTime,
						   Matrix rootTransform)
		{
			UpdateBoneTransforms(time, relativeToCurrentTime);
			UpdateWorldTransforms(rootTransform);
			UpdateSkinTransforms();
		}
		public void UpdateBoneTransforms(TimeSpan time,
			bool relativeToCurrentTime)
		{
			if (currentClip == null)
				throw new InvalidOperationException(
							"AnimationPlayer.Update was called before StartClip");
			// Update the animation position.
			if (relativeToCurrentTime)
			{
				time += currentTime;
				// If we reached the end, loop back to the start.
				while (time >= currentClip.Duration)
					time -= currentClip.Duration;
			}
			if ((time < TimeSpan.Zero) || (time >= currentClip.Duration))
				throw new ArgumentOutOfRangeException("time");
			// If the position moved backwards, reset the keyframe index.
			if (time < currentTime)
			{
				currentKeyframe = 0;
				skinningData.BindPose.CopyTo(boneTransforms, 0);
			}
			currentTime = time;
			// Read keyframe matrices.
			IList<Keyframe> keyframes = currentClip.Keyframes;
			while (currentKeyframe < keyframes.Count)
			{
				Keyframe keyframe = keyframes[currentKeyframe];
				// Stop when we've read up to the current time position.
				if (keyframe.Time > currentTime)
					break;
				// Use this keyframe.
				boneTransforms[keyframe.Bone] = keyframe.Transform;
				currentKeyframe++;
			}
		}
		public void UpdateWorldTransforms(Matrix rootTransform)
		{
			// Root bone.
			worldTransforms[0] = boneTransforms[0] * rootTransform;
			// Child bones.
			for (int bone = 1; bone < worldTransforms.Length; bone++)
			{
				int parentBone = skinningData.SkeletonHierarchy[bone];
				worldTransforms[bone] = boneTransforms[bone] *
											 worldTransforms[parentBone];
			}
		}
		public void UpdateSkinTransforms()
		{
			for (int bone = 0; bone < skinTransforms.Length; bone++)
			{
				skinTransforms[bone] = skinningData.InverseBindPose[bone] *
											worldTransforms[bone];
			}
		}
		public Matrix[] GetBoneTransforms()
		{
			return boneTransforms;
		}
		public Matrix[] GetWorldTransforms()
		{
			return worldTransforms;
		}
		public Matrix[] GetSkinTransforms()
		{
			return skinTransforms;
		}
		public AnimationClip CurrentClip
		{
			get { return currentClip; }
		}
		public TimeSpan CurrentTime
		{
			get { return currentTime; }
		}
	}
}
